package com.hutra.net {
	import com.adobe.serialization.json.JSON;
	import com.adobe.serialization.json.JSONDecoder;
	import com.adobe.serialization.json.JSONEncoder;
	import flash.events.*;
	import flash.net.Socket;
	import flash.system.*;
	import flash.utils.ByteArray;
	import flash.utils.setTimeout;
	import mx.utils.StringUtil;
	import com.temple.utils.types.StringUtils;
	import org.osflash.thunderbolt.Logger;
	
	/**
	 * Flash with Netty server
	 * @version 1.0.0.0
	 * @author hunglx.usth & hoangbt
	 */
	public class JSocket extends EventDispatcher {
		
		public static var NAME:String = "JSocket";
		
		public static var CONNECTED:String = NAME + "Connected";
		public static var DATA:String = NAME + "Data";
		public static var CLOSED:String = NAME + "Closed";
		
		/**
		 * Catch error connect, IO
		 */
		public static var IOERROR:String = NAME + "Errorio";
		
		/**
		 * Catch error parser data
		 */
		public static var ERROR:String = NAME + "Error";
		public static var WAITED:String = NAME + "Waited";
		public static var RECONNECT:String = NAME + "Reconnect";
		public static var RECONNECTED:String = NAME + "Reconnected";
		
		/**
		 *
		 * Creates a new Socket object. If no parameters are specified, an initially disconnected socket
		 * is created. If parameters are specified, a connection is attempted to the specified host and port.
		 *
		 *   Note: It is strongly advised to use the constructor form without parameters, then
		 * add any event listeners, then call the connect method with host
		 * and port parameters. This sequence guarantees that all event listeners will work
		 * properly.
		 */
		private var socket:Socket;
		
		/**
		 * A Boolean value that determines which code page to use to interpret external text files.
		 * When the property is set to false, external text files are interpretted as Unicode.
		 * (These files must be encoded as Unicode when you save them.) When the property is set to
		 * true, external text files are interpretted using the traditional code page of the
		 * operating system running the application. The default value of useCodePage is false.
		 */
		System.useCodePage = false;
		
		/**
		 * JSocket extends from dispatcher, data almost is binary
		 * have to decompile code from socket bytes to chart and then convert chart to object json
		 * Security.loadPolicyFile("http://" + d + ":" + p + "/crossdomain.xml");
		 * Security.loadPolicyFile("xmlsocket://****.com:10843");
		 * [1]
		 * [2]
		 */
		public function JSocket() {
			//[1]
			socket = new Socket();
			try {
				
				socket.addEventListener(Event.CONNECT, connectHandler);
				socket.addEventListener(Event.CLOSE, closeHandler);
				socket.addEventListener(ErrorEvent.ERROR, errorHandler);
				socket.addEventListener(IOErrorEvent.IO_ERROR, ioError);
				socket.addEventListener(SecurityErrorEvent.SECURITY_ERROR, securityErrorHandler);
				socket.addEventListener(ProgressEvent.SOCKET_DATA, dataReceive);
				
			} catch (ex:Error) {
				Logger.debug("JSocket.Error...:" + ex.message);
			}
		}
		
		private function securityErrorHandler(event:SecurityErrorEvent):void {
			dispatchEvent(new JEvent(IOERROR, event));
		}
		
		public function initCrossdomain(a:String, p:int):void {
			Logger.debug("JSocket.initCrossdomain:" + a + ":" + p);
			Security.loadPolicyFile("xmlsocket://" + a + ":" + p);
		}
		
		/**
		 * Connects the socket to the specified host and port.
		 * @param	a as address, such as IP or URL
		 * @param	p as port
		 */
		public function open(a:String, p:int):void {
			Logger.debug("JSocket.open:socket.open:" + a + ":" + p);
			try {
				if (!socket.connected) {
					socket.connect(a, p);
				}
			} catch (e:Error) {
				Logger.debug("JSocket.open:socket.close()" + e.message);
				socket.close();
				dispatchEvent(new JEvent(ERROR, {}));
			}
		}
		
		public function close():void {
			if (socket.connected)
				socket.close();
		}
		
		public function isConnected():Boolean {
			return socket.connected;
		}
		
		/**
		 * JSON input such as:
		 * {
		 * 		i:"123123" as string,
		 * 		c:Command as int,
		 * 		a:Agreement as {...,...,...}
		 * }
		 * Writes a byte to the socket.
		 * @param	json as data like above
		 */
		public function send(value:Object):void {
			var message:String = (new JSONEncoder(value).getString());
			var sp:String = '\n';
			
			var data:ByteArray = new ByteArray();
			data.writeUTFBytes(message);
			socket.writeByte(value.c); // cmd
			socket.writeBytes(data); // data
			socket.writeUTFBytes(sp); // ending of package
			socket.flush(); // clear everything!
		}
		
		/**
		 * Defines the value of the type property of a socketData event object.
		 * [1] Read all message from socket face when received start from bytesAvailable to end of bytesAvailable
		 * [2] Flushes any accumulated message in the socket's output buffer.
		 * [3] Constructs a new JSONDecoder to parse a JSON string into a native object.
		 * [4] Dispatches an event into the event flow. The event target is the EventDispatcher object upon which the dispatchEvent() method is called.
		 * @param	event
		 */
		private var string:String = '';
		
		/**
		 * [{object:value},{object:value},{object:value},{object:value},{object:value}]
		 */
		private var wrap:Array = new Array();
		
		/**
		 *
		 * @param	event
		 */
		private function dataReceive(event:ProgressEvent):void {
			var byte:int;
			//[1]
			while (socket.bytesAvailable) {
				byte = socket.readByte();
				string += ((byte == 0) ? '' : String.fromCharCode(byte));
			}
			//[2]
			socket.flush();
			//[3]
			try {
				if (string == '') {
					dispatchEvent(new JEvent(ERROR, {value: "Socket data receive null!"}));
				} else {
					if (byte == 0) {
						var array:Array = new Array();
						jdataProc(new String(string), array);
						for (var i:int = 0; i < array.length; i++) {
							var object:Object = (new JSONDecoder(array[i], true).getValue());
							wrap.push(object);
						}
					}
				}
			} catch (ex:Error) {
				dispatchEvent(new JEvent(ERROR, {value: "Socket data receive couldn't parser json data! " + ex.message}));
				Logger.debug("Socket.dataReceive.Error.Data:" + string + "\n" + ex.message);
			}
			//[4]
			if (byte == 0) {
				for (var j:int = 0; j < wrap.length; j++)
					dispatchEvent(new JEvent(DATA, wrap[j]));
				clearData(wrap);
				wrap = new Array();
				string = '';
			}
		}
		
		/**
		 * Hủy bộ nhớ
		 * @param	a
		 */
		private function clearData(a:Array):void {
			if (a != null)
				for (var i:int = 0; i < a.length; i++)
					delete a.splice(i, 1);
			a = new Array();
		}
		
		private function checkAndPass(str:String):String {
			//var nn:int = StringUtils.countOf(str, '{');
			//var mm:int = StringUtils.countOf(str, '}');
			var nn:int = StringUtils.countOf(str.substring(0, str.indexOf('"')), '{');
			if (nn > 1) {
				var mm:int = str.substring(0, str.indexOf('"')).length;
				
				if (mm != nn) {
					str = str.substring(1, str.length - 1);
				}
			}
			//try {
			//new JSONDecoder(str, true);
			//}catch (ex:Error) {
			//str = str.substring(1, str.length - 1);
			//}
			return str;
		}
		
		/**
		 * Process data such as: arlkfgaerglaerg{b{1}aawsd{2}sdfsd{3}
		 * abc{data:1}y{base:2}
		 * 	value[1]: {data:1}
		 * 	value[1]: {base:2}
		 *
		 * @param	value
		 * @param	array
		 */
		private function jdataProc(value:String, array:Array):void {
			//[1] loại bỏ ngoặc ngược trước và sau
			if (value.substring(0, 1) == "}")
				value = value.substring(1, value.length);
			if (value.substring(value.length - 1, value.length) == "{")
				value = value.substring(0, value.length - 1);
			
			var re:RegExp = new RegExp("(?=({(?>[^{}]|(?1))+}))");
			if ((value.indexOf("{") > 0 || value.lastIndexOf("}")) && value != "") {
				var m:Array = re.exec(value);
				if (m != null) {
					if (m.length > 1) {
						var sssss:String = m[1];
						//[2] loại bỏ ngoặc trước hoặc sau
						//[[[ aaaaaaaaaaaaaaaaaaa ]]
						var nn:int = StringUtils.countOf(sssss, '{');
						var mm:int = StringUtils.countOf(sssss, '}');
						if (nn > mm) {
							sssss = sssss.substring(1, sssss.length);
						} else if (nn < mm) {
							sssss = sssss.substring(0, sssss.length - 1);
						}
						var lastint:int = sssss.length;
						sssss = checkAndPass(sssss);
						if (lastint != sssss.length) {
							jdataProc(sssss, array);
						} else {
							array.push(sssss);
						}
					}
				} else
					return;
			} else
				return;
			value = value.replace(m[1], '');
			jdataProc(value, array);
		}
		
		/**
		 * Creates an Event object that contains specific information about ioError events.
		 * Event objects are passed as parameters to Event listeners.
		 * @param	event
		 */
		private function ioError(event:IOErrorEvent):void {
			Logger.debug("JSocket.ioError...");
			dispatchEvent(new JEvent(IOERROR, event));
		}
		
		/**
		 * The Event.CONNECT constant defines the value of the type property of a connect event object.
		 * @param	event
		 */
		private function connectHandler(event:Event):void {
			Logger.debug("JSocket.connectHandler...");
			if (socket.connected) {
				dispatchEvent(new JEvent(CONNECTED, event.currentTarget));
			} else {
				dispatchEvent(new JEvent(WAITED, event.currentTarget));
			}
		}
		
		/**
		 * The Event.CLOSE constant defines the value of the type property of a close event object.
		 * @param	event
		 */
		private function closeHandler(event:Event):void {
			Logger.debug("JSocket.closeHandler...");
			dispatchEvent(new JEvent(CLOSED, {}));
		}
		
		/**
		 * Creates an Event object that contains information about error events.
		 * Event objects are passed as parameters to event listeners.
		 * @param	event
		 */
		private function errorHandler(event:ErrorEvent):void {
			Logger.debug("JSocket.errorHandler...");
			dispatchEvent(new JEvent(IOERROR, event));
		}
	}
}